from utils import *

# handEval
def handEval(player):
    myHand = [card.id for card in player.hand.cards]
    boardCards = [card.id for card in Game.community.cards]
    strength = parseEval(pokereval.poker_eval('holdem', [myHand, [255, 255]], extend(boardCards), iterations = 50000))
    if Game.street == 1:
        player.absHS = flopScaleWin(strength)
    elif Game.street == 2:
        player.absHS = turnScaleWin(strength)
    elif Game.street == 3:
        player.absHS = (1000 - strength) / 1000.
    if Game.street > 1:
        shakeWell(Player.leftOpp)
        shakeWell(Player.rightOpp)
    updateHandEval(player)
    f = probFlush(player.hand, Game.community)
    player.draws = f[0] + (1 - f[0]) * .5 * f[1] + probStraight(player.hand, Game.community)
    
def extend(boardCards):
    if len(boardCards) == 3:
        return boardCards + [255, 255]
    elif len(boardCards) == 4:
        return boardCards + [255]
    return boardCards

def updateHandEval(player):
    if Player.leftOpp.folded:
        Player.leftOpp.dist = [0] * 51
    Player.me.adjHS1 = min(sum(Player.leftOpp.dist[:int(round(Player.me.absHS * 50))]), 1)
    if Player.rightOpp.folded:
        Player.rightOpp.dist = [0] * 51
    Player.me.adjHS2 = min(sum(Player.rightOpp.dist[:int(round(Player.me.absHS * 50))]), 1)

def probFlush(hand, board):
    suits = [0] * 4
    for i in hand.cards:
        suits[i.suit] += 1
    for i in board.cards:
        suits[i.suit] += 1
    (shown, have) = (board.size(), max(suits))
    if have >= 5:
        return (1, 0)
    elif (shown, have) == (3, 4):
        return (.19, .35)
    elif (shown, have) == (3, 3):
        return (0, .042)
    elif (shown, have) == (4, 4):
        return (.1957, 0)
    else:
        return (0, 0)

def probStraight(hand, board):
    ranks = [0] * 13
    for i in hand.cards:
        ranks[i.rank] += 1
    for i in board.cards:
        ranks[i.rank] += 1
    total = 0
    if len(board.cards) == 3:
        for j in range(13):
            for k in range(j + 1, 13):
                ranks[j] += 1
                ranks[k] += 1
                if checkStraight(ranks):
                    total += 1
                ranks[j] -= 1
                ranks[k] -= 1
        return total / 156.
    elif len(board.cards) ==  4:
        for j in range(13):
            ranks[j] +=1
            if checkStraight(ranks):
                total += 1
            ranks[j] -= 1
        return total / 13.
    return 0

# oppHandEval
def updateWithFlop(player, behavior1 = [.5, .4, .2], behavior2 = [.3, .4, .3], behavior3 = [.2, .3, .2]):
    raised = findRaise(player.actions[0])
    checked = findCheck(player.actions[0])
    if Game.handId > 30:
        tight = .3 * player.preflopTight + .7 * player.sawFlopPct
    else:
        tight = player.preflopTight
    if raised >= 7 * Game.sb:
        hands = (1., .7 * player.preflopAggro, .6, 2 * player.preflopAggro, .1)
    elif raised > 0:
        hands = (behavior1[0], player.preflopAggro, behavior2[0], tight, behavior3[0])
    elif checked:
        hands = (behavior1[1], player.preflopAggro, behavior2[1], tight, behavior3[1])
    else:
        hands = (behavior1[2], player.preflopAggro, behavior2[2], tight, behavior3[2])
    # (prob1, range1, prob2, range2)
    cardSum = 0
    for i in Game.community.cards:
        cardSum += i.rank
    if cardSum >= 26:
        for i in range(51):
            if i / 50. < hands[1]:
                player.dist[i] += hands[0]
            elif i / 50. < hands[3]:
                player.dist[i] += hands[2]
            else:
                player.dist[i] += hands[4]
    elif cardSum >= 19:
        for i in range(51):
            if i / 50. < hands[1]:
                player.dist[i] += .5 * hands[0] + .5 * hands[2]
            elif i / 50. < hands[3]:
                player.dist[i] += hands[2]
            else:
                player.dist[i] += .5 * hands[4] + .5 * hands[2]
    else:
        player.dist = [1] * 51
    player.dist = scale(player.dist)

def findRaise(actions):
    for i in actions:
        if i.isType('RAISE'):
            return i.val
    return 0

def findCheck(actions):
    if actions[0].isType('CALL') and actions[0].val <= 2 * Game.sb:
        return True
    return False

def lookUpWeight(hands, strength):
    if strength <= hands[1]:
        return hands[0] + (hands[2] - hands[0]) / (hands[1]) * strength
    elif strength <= hands[3]:
        return hands[2] + (hands[4] - hands[2]) / (hands[3] - hands[1]) * (strength - hands[1])
    return hands[4]

def oppFlushDraw(i, j):
    suits = [0] * 4
    for i in Game.community.cards:
        suits[i.suit] += 1
    if i < j and max(suits) == 1:
        return .75 * .04
    elif i < j and max(suits) == 2:
        return .25 * .35
    elif i < j:
        return .25 * 1
    elif max(suits) == 3:
        return .5 * .35 * .5
    return 0

def oppStraightDraw(i, j):
    ranks = [0] * 13
    ranks[i] += 1
    ranks[j] += 1
    for i in Game.community.cards:
        ranks[i.rank] += 1
    total = 0
    for j in range(13):
        for k in range(j + 1, 13):
            ranks[j] += 1
            ranks[k] += 1
            if checkStraight(ranks):
                total += 1
            ranks[j] -= 1
            ranks[k] -= 1
    return total / 156.
    
def checkStraight(ranks):
    for i in range(-1, 9):
        straight = 1
        for j in range(i, i + 5):
            if ranks[j] == 0:
                straight = 0
        if straight == 1:
            return True
    return False   
    
def scale(weights):
    total = sum(weights)
    if total != 0:
        return [float(i) / total for i in weights]
    else:
        return [0] * 51
    
def updateWithAction(player, action):
    if not player.folded:
        for i in range(51):
            prob = oppModel(player, action, .02 * i)
            player.dist[i] = prob * player.dist[i]
        player.dist = scale(player.dist)
    updateHandEval(Player.me)

def adjustRaiseSize(val, player):
    raiseSize = float(val) / Pot.size
    if player.largestBet >= 50 * Game.sb:
        raiseSize = float(min(.4 * val, 40)) / Pot.size
    elif player.largestBet >= 25 * Game.sb:
        raiseSize = float(.7 * val) / Pot.size
    elif player.largestBet >= 12 * Game.sb:
        raiseSize = float(.85 * val) / Pot.size
    if val <= 4 * Game.sb:
        raiseSize *= .5
    elif raiseSize >= .5 and player.largestBet < 12:
        raiseSize = .5 + 2 * (raiseSize - .5)
    return raiseSize

def oppModel(player, action, strength):
    raiseSize = adjustRaiseSize(action.val, player)
    if action.isType('RAISE') and action.val >= 45 * Game.sb and raiseSize > .5:
        if strength <= .5 * (player.bigBetPct / player.sawFlopPct - .05):
            return max(3 - strength * 7, 2)
        elif strength < 1.1 * (player.bigBetPct / player.sawFlopPct - .05):
            return max(2 - strength * 3, 1)
        elif strength < 2 * player.postflopAggro:
            return .4
        else:
            return player.bluff
    if action.isType('RAISE') and raiseSize > .05:
        if strength <= player.postflopAggro:
            return max(3 * (raiseSize - .05)**1.5 - (3 * (.4 + .2 * (raiseSize - .4)) * (raiseSize - .05)**1.5) / player.postflopAggro * strength, player.bluff)
        elif strength <= player.postflopTight:
            return max(3 * (.6 - .2 * (raiseSize - .4)) * (raiseSize - .05)**1.5 - abs(3 * (.6 - .2 * (raiseSize - .4)) * (raiseSize - .05)**1.5 - player.bluff) / (player.postflopTight - player.postflopAggro) * (strength - player.postflopAggro), player.bluff)
        else:
             return player.bluff
    elif action.isType('CALL') and raiseSize >= .05:
        callSize = raiseSize
        if strength <= 1.5 * player.postflopAggro and callSize > .5:
            return callSize
        elif strength <= 1.5 * player.postflopAggro:
            return .35 + .5 * (callSize - .35)
        elif strength <= player.postflopTight:
            return .35
        else:
            return .25
    else:
        if strength <= player.postflopAggro:
            return 2.5 * player.slowplay
        elif strength <= player.postflopTight:
            return 2.5 * player.slowplay + (1 - player.bluff - 2.5 * player.slowplay) / (player.postflopTight - player.postflopAggro) * (strength - player.postflopAggro)
        else: 
            return 1 - player.bluff

def clip(v, vMin, vMax):
    if v < vMin:
        return abs(v)
    elif v > vMax:
        return vMax
    return v

def shakeWell(player):
    if not player.folded:
        newWeights = [0] * 51
        for i in range(51):
            for j in range(i - 7, i + 8):
                newWeights[clip(j, 0, 50)] += 1 * player.dist[i]
        player.dist = scale(newWeights)
